<?php

/**
 * Channel Model
 *
 * @property integer $type
 * @property string path
 * @property integer $parent_id
 * @property integer $weight
 * @property string $name
 * @property string $title
 * @property string $en_title
 * @property string $keywords
 * @property string $description
 * @property string $create_time
 * @property string $bundle
 * @property integer $status
 * @property string $update_time
 * @property integer id
 *
 * @author Hua Yang <htmlcook@gmail.com>
 */
class Channel extends CActiveRecord
{
	/**
	 * @var integer 状态缺省的
	 */
	const STATUS_DEFAULT = 0;

	/**
	 * @var integer 回收站中
	 */
	const STATUS_TRASH = 1;

	/**
	 * 类型空 封面
	 *
	 * @var integer
	 */
	const TYPE_EMPTY = 0;

	/**
	 * 单页面
	 *
	 * @var integer
	 */
	const TYPE_PAGE = 1;

	/**
	 * 列表页面
	 *
	 * @var integer
	 */
	const TYPE_LIST = 2;

	/**
	 * 深度
	 *
	 * @var integer
	 */
	public $depth = 0;

	public $image;

	private $_bundleClass;

	private $_oldType;

	private $_oldName;

	/**
	 * 获取模型
	 *
	 * @param string $className
	 * @return Channel
	 */
	public static function model($className = __CLASS__)
	{
		return parent::model($className);
	}

	/**
	 * 表名称
	 *
	 * @see CActiveRecord::tableName()
	 */
	public function tableName()
	{
		return '{{channel}}';
	}

	/**
	 * 行为
	 *
	 * @see CModel::behaviors()
	 */
	public function behaviors()
	{
		if (!empty(Yii::app()->params['channel.image'])) {
			$channelImage = Yii::app()->params['channel.image'];
			return array(
				'YFileUsageBehavior' => array(
					'class' => 'YFileUsageBehavior',
					'fields' => array(
						'image' => array(
							'location' => 'public://channel',
							'type' => 'image',
							'resize' => isset($channelImage['resize']) ? $channelImage['resize'] : null,
						),
					),
				)
			);
		} else {
			return array();
		}
	}

	/**
	 * 验证规则
	 *
	 * @see CModel::rules()
	 */
	public function rules()
	{
		return array(
			array('title, name', 'required'),
			array('title, name, en_title, keywords', 'length', 'max' => 255),
			array('name', 'match', 'pattern' => '|^[0-9a-z\-]+$|i', 'message' => '{attribute} 只能包含英文和 数字 、-.'),
			array('parent_id, weight', 'numerical', 'integerOnly' => true),
			array('path, name', 'unique'),
			//array('content', 'filter', 'filter' => array($obj = new CHtmlPurifier(), 'purify')),
			array('type', 'in', 'range' => array(self::TYPE_EMPTY, self::TYPE_LIST, self::TYPE_PAGE)),
			array('bundle', 'validateBundle'),
			array('description, image', 'safe'),
			array('id', 'safe', 'on' => 'search'),
		);
	}

	/**
	 * 验证模型
	 */
	public function validateBundle()
	{
		$bundleList = $this->getBundleList();

		if ($this->type == self::TYPE_LIST && empty($this->bundle)) {
			$this->addError('bundle', '类型为列表时，模型不能为空.');
			return;
		}

		if ($this->bundle && $this->type != self::TYPE_PAGE && !isset($bundleList[$this->bundle])) {
			$this->addError('bundle', '模型选择不正确.');
			return;
		}

		if ($this->type == self::TYPE_PAGE) {
			$class = $this->getBundleClass();
			if ($class != 'Page' && !is_subclass_of($class, 'Page')) {
				$this->addError('bundle', '模型不是单页模型.');
			}
			return;
		}
	}


	/**
	 * 属性标签
	 *
	 * @see CModel::attributeLabels()
	 */
	public function attributeLabels()
	{
		return array(
			'id' => 'ID',
			'image' => '栏目特色图',
			'title' => '标题',
			'en_title' => '英文标题',
			'name' => '名称',
			'path' => '栏目路径',
			'keywords' => '栏目关键字',
			'description' => '栏目描述',
			'type' => '类型',
			'bundle' => '模型',
			'weight' => '权重',
			'parent_id' => '父级',
			'create_time' => '创建时间',
			'update_time' => '更新时间',
		);
	}

	/**
	 * 类型列表
	 *
	 * @return array
	 */
	public static function getTypeList()
	{
		return array(
			self::TYPE_EMPTY => '封面',
			self::TYPE_PAGE => '单页面',
			self::TYPE_LIST => '列表',
		);
	}

	public function getPage($force=false)
	{
		if ($this->getIsNewRecord() || (!$force && $this->type != self::TYPE_PAGE)) {
			throw new CException('栏目不存在或者类型不是单页面');
		}

		$modelClass = $this->getBundleClass();
		$staticModel = CActiveRecord::model($modelClass);
		if (!$model = $staticModel->findByAttributes(array('channel_id' => $this->id))) {
			$model = new $modelClass;
			$model->channel_id = $this->id;
			$model->content = '';
			$model->save(false);
		}
		return $model;
	}

	public function getObject()
	{
		if ($this->getIsNewRecord() || $this->type != self::TYPE_List) {
			throw new CException('栏目不存在或者类型不是列表');
		}
	}

	public function getBundleList()
	{
		static $bundleList;
		if (!isset($bundleList)) {
			$bundles = $this->getBundles();
			foreach ($bundles as $className => $params) {
				if (!isset($params['label'])) {
					$params['label'] = $className;
				}
				$bundleList[$className] = $params['label'];
			}
		}
		return $bundleList;
	}

	/**
	 * @return array
	 */
	public function getBundles()
	{
		static $bundles;
		if (!isset($bundles)) {
			if (isset(Yii::app()->params['content.bundles'])) {
				$bundles = (array) Yii::app()->params['content.bundles'];
			} else {
				$bundles = array(
					'Article' => array(
						'label' => '文章',
					)
				);
			}
		}
		return $bundles;
	}

	/**
	 * 获取模型列表
	 *
	 * @return array
	 */
	public static function getModelList()
	{
		static $models;
		if (!isset($models)) {
			return array();
		}
		return $models;
	}

	/**
	 * 根据模型获取栏目
	 *
	 * @param string $modelClass
	 * @return array
	 */
	public static function getChannelsByModel($modelClass)
	{
		static $cache = array();
		if (!isset($cache[$modelClass])) {
			$children = self::model()->getChildren(-1);
			$channels = array();

			foreach ($children as $models) {
				foreach ($models as $model) {
					if ($model->getBundleClass() === $modelClass) {
						$channels[] = $model;
					}
				}
			}
			$cache[$modelClass] = $channels;
		}
		return $cache[$modelClass];
	}

	/**
	 * 根据id更新权重
	 *
	 * @param integer $id
	 * @param integer $weight
	 */
	public static function updateWeightByPk($id, $weight)
	{
		$weight = intval($weight);
		$id = intval($id);
		self::model()->updateByPk($id, array('weight' => $weight));
	}

	/**
	 * 获取父节点
	 *
	 * @return Channel|NULl
	 */
	public function getParent()
	{
		return self::get($this->parent_id);
	}

	/**
	 * 获取所有父节点
	 *
	 * @return array
	 */
	public function getParentsAll()
	{
		$parents = array();
		$parentId = $this->parent_id;
		while ($parent = self::get($parentId)) {
			$parents[] = $parent;
			$parentId = $parent->parent_id;
		}

		$parents = array_reverse($parents);
		return $parents;
	}

	/**
	 * 获取所有后代子节点
	 *
	 * @return Channel[]
	 */
	public function getChildrenAll()
	{
		if ($this->getIsNewRecord()) {
			return array();
		}

		$children[] = $this;

		while ($child = current($children)) {
			foreach ($this->getChildren($child->id) as $model) {
				$children[] = $model;
			}
			next($children);
		}

		array_shift($children);

		return $children;
	}

	/**
	 * 根据父ID获取子节点
	 *
	 * @param mixed $parent -1获取所有 null 获取自己的
	 * @return array
	 */
	public function getChildren($parent = null)
	{
		if ($parent === null) {
			if ($this->getIsNewRecord()) {
				return array();
			}
			$parent = $this->id;
		}

		static $children;
		if (!isset($children) && ($children = Yii::app()->getCache()->get('channel_children')) === false) {
			$children = array();
			foreach ($this->findAll(array('order' => 'weight, id')) as $model) {
				$children[$model->parent_id][$model->id] = $model;
			}
			Yii::app()->getCache()->set('channel_children', $children);
		}
		return $parent === -1 ? $children : (isset($children[$parent]) ? $children[$parent] : array());
	}

	/**
	 * 是否有子节点
	 *
	 * @return boolean
	 */
	public function hasChildren()
	{
		return $this->getChildren() ? true : false;
	}

	/**
	 * 生成树 根据父ID, 最大深度
	 *
	 * @param integer $parent
	 * @param integer|NULl $maxDepth
	 * @return Channel[]
	 */
	public function getTree($parent = 0, $maxDepth = null)
	{

		$children = $this->getChildren(-1);
		if ($maxDepth === null) {
			$maxDepth = count($children);
		}

		$processParents = array();
		$tree = array();

		$processParents[] = $parent;
		while (count($processParents)) {
			$parent = array_pop($processParents);
			$depth = count($processParents);

			if ($maxDepth > $depth && !empty($children[$parent])) {
				$hasChildren = false;
				$child = current($children[$parent]);

				do {
					if (empty($child)) {
						break;
					}

					$child->depth = $depth;
					$tree[] = $child;

					if (!empty($children[$child->id])) {
						$hasChildren = true;
						$processParents[] = $parent;
						$processParents[] = $child->id;
						reset($children[$child->id]);
						next($children[$parent]);
						break;
					}
				} while ($child = next($children[$parent]));

				if (!$hasChildren) {
					reset($children[$parent]);
				}
			}
		}

		foreach ($tree as $index => $model) {
			if (isset($this->status) && $model->status != $this->status) {
				unset($tree[$index]);
			}
		}

		return $tree;
	}

	/**
	 * 生成树列表
	 *
	 * @param mixed $parent
	 * @param string $spacer 分隔符 会按照深度
	 * @return array
	 */
	public function getTreeList($parent = -1, $spacer = ' — ')
	{
		$list = array();
		foreach ($this->getTree($parent) as $model) {
			$list[$model->id] = str_repeat($spacer, $model->depth) . ' ' . $model->title;
		}
		return $list;
	}

	/**
	 * 获取栏目 根据id或者栏目名
	 *
	 * @param mixed $value 数字则根据ID 字符串则根据name
	 * @return Channel|NUll
	 */
	public static function get($value)
	{
		static $cache = array();

		if (!$value) {
			return null;
		}

		if ($value instanceof Channel) {
			return $value;
		}

		if (isset($cache[$value])) {
			return $cache[$value];
		}

		$children = self::model()->getChildren(-1);

		if (is_numeric($value)) {
			$attribute = 'id';
		} else {
			$attribute = 'name';
		}

		foreach ($children as $models) {
			if ($attribute === 'id') {
				if (isset($models[$value])) {
					$channel = $models[$value];
					break;
				} else {
					continue;
				}
			} else {
				foreach ($models as $model) {
					if ($model->name == $value) {
						$channel = $model;
						break;
					}
				}
			}
		}

		if (isset($channel)) {
			$cache[$channel->id] = $channel;
			$cache[$channel->name] = & $cache[$channel->id];
			return $channel;
		}
	}

	/**
	 * 获取栏目路径
	 *
	 * @return string
	 */
	public function resolvePath()
	{
		$parents = $this->getParentsAll();
		foreach ($parents as $parent) {
			$path[] = $parent->name;
		}

		$path[] = $this->name;
		return implode('/', $path);
	}

	/**
	 * 获取顶层父节点
	 *
	 * @return false|Channel
	 */
	public function getTopLevelParent()
	{
		$parents = $this->getParentsAll();
		if ($parents) {
			return current($parents);
		} else {
			return $this;
		}
	}

	/**
	 * 获取模型名
	 *
	 * @param null $bundle
	 * @return string
	 */
	public function getBundleName($bundle = null)
	{
		if ($bundle === null) {
			$bundle = $this->bundle;
		}

		$list = $this->getBundleList();
		return isset($list[$bundle]) ? $list[$bundle] : $bundle;
	}

	/**
	 * 获取权重列表，根据分类数目
	 *
	 * @return array
	 */
	public function getWeightList()
	{
		$total = array(0);
		foreach ($this->getChildren(-1) as $children) {
			$total[] = count($children);
		}
		$count = max($total);
		$range = range(-$count, $count);
		return array_combine($range, $range);
	}

	/**
	 * 获取栏目路径
	 * @return string
	 */
	public function getPath()
	{
		if ($this->path) {
			return $this->path;
		}

		static $cache = array();
		if (isset($cache[$this->id])) {
			return $cache[$this->id];
		}
		$parents = $this->getParentsAll();
		foreach ($parents as $parent)
			$path[] = $parent->name;

		$path[] = $this->name;
		$cache[$this->id] = implode('/', $path);
		return $cache[$this->id];
	}

	/**
	 * 获取栏目内容操作链接
	 *
	 * @return string
	 */
	public function getContentActionLink()
	{
		$htmlOptions = array('class' => 'btn btn-link');;
		$links = array();
		$url = array('/content/index', 'channel_id' => $this->id);
		if ($this->type != self::TYPE_PAGE){
			if ($this->type == self::TYPE_EMPTY) {
				$url = array('/content/channel', 'channel_id' => $this->id);
			}
			$links[] = CHtml::link('<i class="fa fa-list"></i> 管理内容', $url, $htmlOptions);
		}

		if ($this->type == self::TYPE_LIST) {
			$links[] = CHtml::link(' <i class="fa fa-plus"></i> 创建内容', array('/content/create', 'channel_id' => $this->id), $htmlOptions);
		} elseif ($this->type == self::TYPE_PAGE) {
			$links[] = CHtml::link('<i class="fa fa-pencil"></i> 更新页面', array('/content/page', 'channel_id' => $this->id), $htmlOptions);
		}

		return implode("\t", $links);
	}

	/**
	 * 获取内容模型导航
	 *
	 * @param integer $parent 栏目id
	 * @return array
	 */
	public function getContentMenuItems($parent = 0)
	{
		$items = array();
		$children = Channel::model()->getChildren($parent);
		foreach ($children as $channel) {
			if ($channel->status == Channel::STATUS_TRASH) {
				continue;
			}

			if ($channel->type == self::TYPE_LIST) {
				$url = array('/content/index', 'channel_id' => $channel->id);
			} elseif ($channel->type == self::TYPE_PAGE) {
				$url = array('/content/page', 'channel_id' => $channel->id);
			} else {
				$url = array('/content/channel', 'channel_id' => $channel->id);
			}

			$item = array('label' => $channel->title, 'url' => $url);
			if ($channel->type == self::TYPE_LIST) {
				$item['jstree']['icon'] = 'fa fa-list';
			} elseif ($channel->type == self::TYPE_PAGE) {
				$item['jstree']['icon'] = 'fa fa-file';
			} else {
				$item['jstree']['icon'] = 'fa fa-folder-open';
				$item['jstree']['disabled'] = true;
			}
			$childrenItems = $this->getContentMenuItems($channel->id);

			if ($childrenItems !== array()) {
				$item['jstree']['opened'] = true;
				$item['items'] = $childrenItems;
			}

			$item['id'] = $channel->id;
			$items[] = $item;
		}
		return $items;
	}

	public function getBundleClass()
	{
		if (!isset($this->_bundleClass)) {
			if (($pos = strrpos($this->bundle, '.') !== false)) {
				Yii::import($this->bundle);
				$this->_bundleClass = substr($this->bundle, $pos);
			} else {
				$this->_bundleClass = $this->bundle;
			}
		}

		return $this->_bundleClass;
	}

	/**
	 * 获取内容对象模型
	 *
	 * @param boolean $static
	 * @param boolean $applyChannel
	 * @throws CException
	 * @return CActiveRecord|null
	 */
	public function getObjectModel($static = true, $applyChannel = true)
	{
		if ($this->getIsNewRecord()) {
			throw new CException('栏目还没有创建');
		}

		$modelClass = $this->getBundleClass();

		if (!$modelClass) {
			throw new CException('没有发现栏目模型');
		}

		if ($static) {
			$model = CActiveRecord::model($modelClass);
		} else {
			$model = new $modelClass('search');
			$model->unsetAttributes();
		}

		if (($model instanceof Node) && $applyChannel) {
			$model->byChannel($this->id);
		}

		return $model;
	}

	/**
	 * 删除缓存
	 */
	public function deleteCache()
	{
		Yii::app()->getCache()->delete('channel_children');
	}

	/**
	 * 改变状态
	 *
	 * @param integer $status
	 * @return boolean
	 */
	public function changeStatus($status)
	{
		if (in_array($status, array(self::STATUS_DEFAULT, self::STATUS_TRASH))) {
			if ($this->status != $status) {
				$this->status = $status;
				foreach ($this->getChildrenAll() as $child) {
					$child->status = $status;
					$child->update('status');
				}
				$this->update('status');
				return true;
			}
		}

		return false;
	}

	/**
	 * @param null|string $name
	 * @return bool
	 */
	public function isSystemChannel($name=null)
	{
		static $systemChannels;
		if (!isset($systemChannels)) {
			if (isset(Yii::app()->params['system.channels'])) {
				$systemChannels = Yii::app()->params['system.channels'];
			} else {
				$systemChannels = array();
			}
		}

		if ($name === null) {
			$name = $this->name;
		}
		return in_array($name, $systemChannels);
	}

	protected function afterFind()
	{
		parent::afterFind();
		$this->_oldType = $this->type;
		$this->_oldName = $this->name;
	}

	/**
	 * 验证之前
	 *
	 * @see CModel::beforeValidate()
	 */
	protected function beforeValidate()
	{
		if (parent::beforeValidate()) {
			if (!$this->getIsNewRecord()) {
				if ($this->isSystemChannel()) {
					$this->name = $this->_oldName;
				}
			}

			if ($this->type == self::TYPE_PAGE) {
				$this->bundle = 'Page';
			}

			$this->title = trim($this->title);
			$this->en_title = trim($this->en_title);
			$this->name = preg_replace('/\s+/i', '-', trim(strtolower($this->name)));
			$this->bundle = trim($this->bundle);
			$this->path = $this->resolvePath();

			return true;
		} else {
			return false;
		}
	}

	/**
	 * 保存之前
	 *
	 * @see CActiveRecord::beforeSave()
	 */
	protected function beforeSave()
	{
		if (parent::beforeSave()) {
			if ($this->getIsNewRecord()) {
				$this->create_time = time();
			} else {
				if ($this->_oldType !== $this->type) {
					if ($this->_oldType == self::TYPE_PAGE) {
						$this->getPage(true)->delete();
					} elseif ($this->_oldType == self::TYPE_LIST) {
						if ($this->getObjectModel(true)->count() > 0) {
							$this->addError('type', '栏目下还有数据，不能转变类型。');
							return false;
						}
					}
				}
			}

			$this->update_time = time();

			foreach ($this->getChildrenAll() as $child) {
				if ($child->id == $this->parent_id) {
					unset($this->parent_id);
					break;
				}
			}

			if (trim($this->parent_id) === '') {
				$this->parent_id = 0;
			}

			if ($this->parent_id == $this->id) {
				unset($this->parent_id);
			}

			if ($this->keywords) {
				$keywords = preg_split('/[\s|,]+/', $this->keywords, -1, PREG_SPLIT_NO_EMPTY);
				$this->keywords = trim(implode(',', $keywords), ',');
			}

			return true;
		} else {
			return false;
		}
	}

	/**
	 * 删除之前
	 *
	 * @see CActiveRecord::beforeDelete()
	 */
	protected function beforeDelete()
	{
		if (parent::beforeDelete()) {
			if ($this->hasChildren()) {
				return false;
			}
			if ($this->isSystemChannel()) {
				return false;
			}
			return true;
		} else {
			return false;
		}
	}


	/**
	 * 删除之后
	 *
	 * @see CActiveRecord::afterDelete()
	 */
	protected function afterDelete()
	{
		parent::afterDelete();
		$this->deleteCache();

		Yii::app()->getWatchDog()->write('channel_delete',
			'栏目：%id %title %name 删除',
			array('%id'=>$this->id, '%title' => $this->title, '%name' =>$this->name));
		/*
		if ($this->type == self::TYPE_LIST && ($modelClass = $this->model)) {
			$staticModel = CActiveRecord::model($modelClass);
			if (!$staticModel instanceof Node) {
				return;
			}
			if ($staticModel->hasAttribute('channel_id')) {
				$staticModel->bulkDelete('channel_id=:channel_id', array(':channel_id' => $this->id));
			}
		}
		*/
	}

	/**
	 * 保存之后
	 *
	 * @see CActiveRecord::afterSave()
	 */
	protected function afterSave()
	{
		parent::afterSave();
		$this->deleteCache();
	}
}